#include"MersenneTwister.hpp"
#include<CL/opencl.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

#define MAX_SOURCE_SIZE 100000

cl_context hContext;
cl_device_id cdDevice;
cl_program hProgram;
cl_command_queue hCmdQueue;
cl_kernel ckKernel;

void Cleanup(int );

cl_int selectPlatform(cl_platform_id* clSelectedPlatform){
	
	 char chBuffer[1024];
	cl_uint numPlatforms;
	cl_int ciErrNum = CL_SUCCESS;
	*clSelectedPlatform = NULL;
	cl_platform_id *platformIds;

	ciErrNum = clGetPlatformIDs (0, NULL, &numPlatforms);
	if (ciErrNum != CL_SUCCESS)
    {
        printf("Error: Failed to get number of OpenCL Platform!\n");
        return ciErrNum;
    }
	 else 
    {
        if(numPlatforms == 0)
        {
            printf("No OpenCL platform found!\n\n");
            return -2000;
        }
		 else 
        {
            // if there's a platform or more, make space for ID's
            if ((platformIds = (cl_platform_id*)malloc(numPlatforms * sizeof(cl_platform_id))) == NULL)
            {
                printf("Failed to allocate memory for cl_platform ID's!\n\n");
                return -3000;
            }

            // get platform info for each platform and trap the NVIDIA platform if found
            ciErrNum = clGetPlatformIDs (numPlatforms, platformIds, NULL);
            for(cl_uint i = 0; i < numPlatforms; ++i)
            {
                ciErrNum = clGetPlatformInfo (platformIds[i], CL_PLATFORM_NAME, 1024, &chBuffer, NULL);
				printf(chBuffer);

                if(ciErrNum == CL_SUCCESS)
                {
                    if(strstr(chBuffer, "NVIDIA") != NULL)
                    {
                        *clSelectedPlatform = platformIds[i];
                        break;
                    }
					else if(strstr(chBuffer, "AMD") != NULL)
                    {
                        *clSelectedPlatform = platformIds[i];
                        break;
                    }
                }
            }

            // default to zeroeth platform if NVIDIA not found
            if(*clSelectedPlatform == NULL)
            {
                printf("WARNING: NVIDIA or AMD OpenCL platform not found - defaulting to first platform!\n\n");
                *clSelectedPlatform = platformIds[0];
            }

            free(platformIds);
        }

	}
	printf("\n\nNumber of OpenCL Platforms = %d \n\n", numPlatforms);

	//Get the PlatformIds
	platformIds = new cl_platform_id[numPlatforms];
	ciErrNum = clGetPlatformIDs(numPlatforms, platformIds, NULL);
	if (ciErrNum != CL_SUCCESS)
    {
        printf("Error: Failed to get OpenCL PlatformIds!\n");
        return ciErrNum;
    }

	return ciErrNum;
}
int InitializeOpenCL(char* fullFilePath, int filePathSize)
{	
	cl_int ciErrNum = CL_SUCCESS;
	cl_platform_id clPlatform;

	//Get OpenCL Platform 
	ciErrNum = selectPlatform (&clPlatform);	

	//Get GPU device from the platform
	ciErrNum = clGetDeviceIDs(clPlatform, CL_DEVICE_TYPE_GPU, 1, &cdDevice, NULL); 
	if (ciErrNum != CL_SUCCESS)															
    {
        printf("Error: Failed to get GPU OpenCL Device!\n");
		printf("Checking for CPU Device!\n");

		ciErrNum = clGetDeviceIDs(clPlatform, CL_DEVICE_TYPE_CPU, 1, &cdDevice, NULL); 
		if (ciErrNum != CL_SUCCESS)														
		{
	        printf("Error: Failed to get CPU OpenCL Device!\n");
			return ciErrNum;			
		}
	}

		char chDeviceName[256];
		clGetDeviceInfo(cdDevice, CL_DEVICE_NAME, 256, chDeviceName, NULL);

		printf("Device Name : ");
		printf(chDeviceName);
		printf("\n\n");
	
	//Create Context
	hContext = clCreateContext ( 0, 1, &cdDevice, NULL, NULL, NULL);
	
	//Set the kernel file path
	FILE *fp;
	char *fileName;
	fileName = (char*)malloc(filePathSize);
	fileName = fullFilePath;
	
	char *source_str;
	size_t source_size;

	/* Load the source code containing the kernel*/
	fp = fopen(fileName, "r");
	if (!fp) {
		printf("Failed to load kernel.\n");
		exit(1);
	}

	source_str = (char*)malloc(MAX_SOURCE_SIZE);
	source_size = fread( source_str, 1, MAX_SOURCE_SIZE, fp);
	fclose( fp );

	//Create and compile program
	hProgram = clCreateProgramWithSource(hContext, 1, (const char **)&source_str, (const size_t *)&source_size, &ciErrNum);

	if (ciErrNum != CL_SUCCESS)
    {
        printf("Error: Failed to create program\n");
        return ciErrNum;
    }
    free(source_str);

	/* Build Kernel Program */
	ciErrNum = clBuildProgram(hProgram, 0, NULL, NULL, NULL, NULL);
	if (ciErrNum != CL_SUCCESS)
    {
        printf("Error: Failed to build program\n");
        size_t length;
		char buffer[2048];
		clGetProgramBuildInfo(hProgram, cdDevice, CL_PROGRAM_BUILD_LOG, sizeof(buffer), buffer, &length);
		
		printf("Build Log: %s\n", buffer);
		getchar();
        return ciErrNum;
    }

	return ciErrNum;
}


int run_program(){
	cl_int ciErrNum = CL_SUCCESS;

	char fileName[] = "MersenneTwister.cl";
	int filePathSize = 18;

	//Initialize the OpenCL device, Context and Program
	ciErrNum = InitializeOpenCL(fileName, filePathSize);
	if (ciErrNum != CL_SUCCESS)
    {
        printf("Error: Failed to Initialize OpenCL Program\n");
        return ciErrNum;
    }

	printf("Initialized OpenCL Program\n");

	//Declare OpenCL Runtime variables
	cl_command_queue hCmdQueue;

	//Create Command Queue
	hCmdQueue = clCreateCommandQueue(hContext, cdDevice, 0, &ciErrNum);
	if (ciErrNum != CL_SUCCESS)
    {
        printf("Error: Failed to Initialize OpenCL Program\n");
        return ciErrNum;
	}
		
	ckKernel = clCreateKernel(hProgram, "test_KERNEL", &ciErrNum);

	size_t globalWorkSize[1];
	globalWorkSize[0] = 1;

	ciErrNum = clEnqueueNDRangeKernel(hCmdQueue, ckKernel, 1, NULL, globalWorkSize, NULL, 0, NULL, NULL);
	printf("Completed execution\n\n");

	Cleanup(0);

	return 0;  
}


void Cleanup(int iExitCode){

	if(ckKernel)clReleaseKernel(ckKernel);
	if(hProgram)clReleaseProgram(hProgram);
	if(hCmdQueue)clReleaseCommandQueue(hCmdQueue);
	if(hContext)clReleaseContext(hContext);

	// release buffers

	// free host variables
	 
}

int main(int argc, const char** argv){
	run_program();

}

